( function () {
const _taskCache = new WeakMap();

class Rhino3dmLoader extends THREE.Loader {
  constructor(manager) {
    super(manager);
    this.libraryPath = '';
    this.libraryPending = null;
    this.libraryBinary = null;
    this.libraryConfig = {};
    this.url = '';
    this.workerLimit = 4;
    this.workerPool = [];
    this.workerNextTaskID = 1;
    this.workerSourceURL = '';
    this.workerConfig = {};
    this.materials = [];
    this.warnings = [];
  }

  setLibraryPath(path) {
    this.libraryPath = path;
    return this;
  }

  setWorkerLimit(workerLimit) {
    this.workerLimit = workerLimit;
    return this;
  }

  load(url, onLoad, onProgress, onError) {
    const loader = new THREE.FileLoader(this.manager);
    loader.setPath(this.path);
    loader.setResponseType('arraybuffer');
    loader.setRequestHeader(this.requestHeader);
    this.url = url;
    loader.load(url, buffer => {
      // Check for an existing task using this buffer. A transferred buffer cannot be transferred
      // again from this thread.
      if (_taskCache.has(buffer)) {
        const cachedTask = _taskCache.get(buffer);

        return cachedTask.promise.then(onLoad).catch(onError);
      }

      this.decodeObjects(buffer, url).then(result => {
        result.userData.warnings = this.warnings;
        onLoad(result);
      }).catch(e => onError(e));
    }, onProgress, onError);
  }

  debug() {
    console.log('Task load: ', this.workerPool.map(worker => worker._taskLoad));
  }

  decodeObjects(buffer, url) {
    let worker;
    let taskID;
    const taskCost = buffer.byteLength;

    const objectPending = this._getWorker(taskCost).then(_worker => {
      worker = _worker;
      taskID = this.workerNextTaskID++;
      return new Promise((resolve, reject) => {
        worker._callbacks[taskID] = {
          resolve,
          reject
        };
        worker.postMessage({
          type: 'decode',
          id: taskID,
          buffer
        }, [buffer]); // this.debug();
      });
    }).then(message => this._createGeometry(message.data)).catch(e => {
      throw e;
    }); // Remove task from the task list.
    // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)


    objectPending.catch(() => true).then(() => {
      if (worker && taskID) {
        this._releaseTask(worker, taskID); //this.debug();

      }
    }); // Cache the task result.

    _taskCache.set(buffer, {
      url: url,
      promise: objectPending
    });

    return objectPending;
  }

  parse(data, onLoad, onError) {
    this.decodeObjects(data, '').then(result => {
      result.userData.warnings = this.warnings;
      onLoad(result);
    }).catch(e => onError(e));
  }

  _compareMaterials(material) {
    const mat = {};
    mat.name = material.name;
    mat.color = {};
    mat.color.r = material.color.r;
    mat.color.g = material.color.g;
    mat.color.b = material.color.b;
    mat.type = material.type;

    for (let i = 0; i < this.materials.length; i++) {
      const m = this.materials[i];
      const _mat = {};
      _mat.name = m.name;
      _mat.color = {};
      _mat.color.r = m.color.r;
      _mat.color.g = m.color.g;
      _mat.color.b = m.color.b;
      _mat.type = m.type;

      if (JSON.stringify(mat) === JSON.stringify(_mat)) {
        return m;
      }
    }

    this.materials.push(material);
    return material;
  }

  _createMaterial(material) {
    if (material === undefined) {
      return new THREE.MeshStandardMaterial({
        color: new THREE.Color(1, 1, 1),
        metalness: 0.8,
        name: 'default',
        side: 2
      });
    }

    const _diffuseColor = material.diffuseColor;
    const diffusecolor = new THREE.Color(_diffuseColor.r / 255.0, _diffuseColor.g / 255.0, _diffuseColor.b / 255.0);

    if (_diffuseColor.r === 0 && _diffuseColor.g === 0 && _diffuseColor.b === 0) {
      diffusecolor.r = 1;
      diffusecolor.g = 1;
      diffusecolor.b = 1;
    } // console.log( material );


    const mat = new THREE.MeshStandardMaterial({
      color: diffusecolor,
      name: material.name,
      side: 2,
      transparent: material.transparency > 0 ? true : false,
      opacity: 1.0 - material.transparency
    });
    const textureLoader = new THREE.TextureLoader();

    for (let i = 0; i < material.textures.length; i++) {
      const texture = material.textures[i];

      if (texture.image !== null) {
        const map = textureLoader.load(texture.image);

        switch (texture.type) {
          case 'Diffuse':
            mat.map = map;
            break;

          case 'Bump':
            mat.bumpMap = map;
            break;

          case 'Transparency':
            mat.alphaMap = map;
            mat.transparent = true;
            break;

          case 'Emap':
            mat.envMap = map;
            break;
        }
      }
    }

    return mat;
  }

  _createGeometry(data) {
    // console.log(data);
    const object = new THREE.Object3D();
    const instanceDefinitionObjects = [];
    const instanceDefinitions = [];
    const instanceReferences = [];
    object.userData['layers'] = data.layers;
    object.userData['groups'] = data.groups;
    object.userData['settings'] = data.settings;
    object.userData['objectType'] = 'File3dm';
    object.userData['materials'] = null;
    object.name = this.url;
    let objects = data.objects;
    const materials = data.materials;

    for (let i = 0; i < objects.length; i++) {
      const obj = objects[i];
      const attributes = obj.attributes;

      switch (obj.objectType) {
        case 'InstanceDefinition':
          instanceDefinitions.push(obj);
          break;

        case 'InstanceReference':
          instanceReferences.push(obj);
          break;

        default:
          let _object;

          if (attributes.materialIndex >= 0) {
            const rMaterial = materials[attributes.materialIndex];

            let material = this._createMaterial(rMaterial);

            material = this._compareMaterials(material);
            _object = this._createObject(obj, material);
          } else {
            const material = this._createMaterial();

            _object = this._createObject(obj, material);
          }

          if (_object === undefined) {
            continue;
          }

          const layer = data.layers[attributes.layerIndex];
          _object.visible = layer ? data.layers[attributes.layerIndex].visible : true;

          if (attributes.isInstanceDefinitionObject) {
            instanceDefinitionObjects.push(_object);
          } else {
            object.add(_object);
          }

          break;
      }
    }

    for (let i = 0; i < instanceDefinitions.length; i++) {
      const iDef = instanceDefinitions[i];
      objects = [];

      for (let j = 0; j < iDef.attributes.objectIds.length; j++) {
        const objId = iDef.attributes.objectIds[j];

        for (let p = 0; p < instanceDefinitionObjects.length; p++) {
          const idoId = instanceDefinitionObjects[p].userData.attributes.id;

          if (objId === idoId) {
            objects.push(instanceDefinitionObjects[p]);
          }
        }
      } // Currently clones geometry and does not take advantage of instancing


      for (let j = 0; j < instanceReferences.length; j++) {
        const iRef = instanceReferences[j];

        if (iRef.geometry.parentIdefId === iDef.attributes.id) {
          const iRefObject = new THREE.Object3D();
          const xf = iRef.geometry.xform.array;
          const matrix = new THREE.Matrix4();
          matrix.set(xf[0], xf[1], xf[2], xf[3], xf[4], xf[5], xf[6], xf[7], xf[8], xf[9], xf[10], xf[11], xf[12], xf[13], xf[14], xf[15]);
          iRefObject.applyMatrix4(matrix);

          for (let p = 0; p < objects.length; p++) {
            iRefObject.add(objects[p].clone(true));
          }

          object.add(iRefObject);
        }
      }
    }

    object.userData['materials'] = this.materials;
    return object;
  }

  _createObject(obj, mat) {
    const loader = new THREE.BufferGeometryLoader();
    const attributes = obj.attributes;

    let geometry, material, _color, color;

    switch (obj.objectType) {
      case 'Point':
      case 'PointSet':
        geometry = loader.parse(obj.geometry);

        if (geometry.attributes.hasOwnProperty('color')) {
          material = new THREE.PointsMaterial({
            vertexColors: true,
            sizeAttenuation: false,
            size: 2
          });
        } else {
          _color = attributes.drawColor;
          color = new THREE.Color(_color.r / 255.0, _color.g / 255.0, _color.b / 255.0);
          material = new THREE.PointsMaterial({
            color: color,
            sizeAttenuation: false,
            size: 2
          });
        }

        material = this._compareMaterials(material);
        const points = new THREE.Points(geometry, material);
        points.userData['attributes'] = attributes;
        points.userData['objectType'] = obj.objectType;

        if (attributes.name) {
          points.name = attributes.name;
        }

        return points;

      case 'Mesh':
      case 'Extrusion':
      case 'SubD':
      case 'Brep':
        if (obj.geometry === null) return;
        geometry = loader.parse(obj.geometry);

        if (geometry.attributes.hasOwnProperty('color')) {
          mat.vertexColors = true;
        }

        if (mat === null) {
          mat = this._createMaterial();
          mat = this._compareMaterials(mat);
        }

        const mesh = new THREE.Mesh(geometry, mat);
        mesh.castShadow = attributes.castsShadows;
        mesh.receiveShadow = attributes.receivesShadows;
        mesh.userData['attributes'] = attributes;
        mesh.userData['objectType'] = obj.objectType;

        if (attributes.name) {
          mesh.name = attributes.name;
        }

        return mesh;

      case 'Curve':
        geometry = loader.parse(obj.geometry);
        _color = attributes.drawColor;
        color = new THREE.Color(_color.r / 255.0, _color.g / 255.0, _color.b / 255.0);
        material = new THREE.LineBasicMaterial({
          color: color
        });
        material = this._compareMaterials(material);
        const lines = new THREE.Line(geometry, material);
        lines.userData['attributes'] = attributes;
        lines.userData['objectType'] = obj.objectType;

        if (attributes.name) {
          lines.name = attributes.name;
        }

        return lines;

      case 'TextDot':
        geometry = obj.geometry;
        const ctx = document.createElement('canvas').getContext('2d');
        const font = `${geometry.fontHeight}px ${geometry.fontFace}`;
        ctx.font = font;
        const width = ctx.measureText(geometry.text).width + 10;
        const height = geometry.fontHeight + 10;
        const r = window.devicePixelRatio;
        ctx.canvas.width = width * r;
        ctx.canvas.height = height * r;
        ctx.canvas.style.width = width + 'px';
        ctx.canvas.style.height = height + 'px';
        ctx.setTransform(r, 0, 0, r, 0, 0);
        ctx.font = font;
        ctx.textBaseline = 'middle';
        ctx.textAlign = 'center';
        color = attributes.drawColor;
        ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${color.a})`;
        ctx.fillRect(0, 0, width, height);
        ctx.fillStyle = 'white';
        ctx.fillText(geometry.text, width / 2, height / 2);
        const texture = new THREE.CanvasTexture(ctx.canvas);
        texture.minFilter = THREE.LinearFilter;
        texture.wrapS = THREE.ClampToEdgeWrapping;
        texture.wrapT = THREE.ClampToEdgeWrapping;
        material = new THREE.SpriteMaterial({
          map: texture,
          depthTest: false
        });
        const sprite = new THREE.Sprite(material);
        sprite.position.set(geometry.point[0], geometry.point[1], geometry.point[2]);
        sprite.scale.set(width / 10, height / 10, 1.0);
        sprite.userData['attributes'] = attributes;
        sprite.userData['objectType'] = obj.objectType;

        if (attributes.name) {
          sprite.name = attributes.name;
        }

        return sprite;

      case 'Light':
        geometry = obj.geometry;
        let light;

        switch (geometry.lightStyle.name) {
          case 'LightStyle_WorldPoint':
            light = new THREE.PointLight();
            light.castShadow = attributes.castsShadows;
            light.position.set(geometry.location[0], geometry.location[1], geometry.location[2]);
            light.shadow.normalBias = 0.1;
            break;

          case 'LightStyle_WorldSpot':
            light = new THREE.SpotLight();
            light.castShadow = attributes.castsShadows;
            light.position.set(geometry.location[0], geometry.location[1], geometry.location[2]);
            light.target.position.set(geometry.direction[0], geometry.direction[1], geometry.direction[2]);
            light.angle = geometry.spotAngleRadians;
            light.shadow.normalBias = 0.1;
            break;

          case 'LightStyle_WorldRectangular':
            light = new THREE.RectAreaLight();
            const width = Math.abs(geometry.width[2]);
            const height = Math.abs(geometry.length[0]);
            light.position.set(geometry.location[0] - height / 2, geometry.location[1], geometry.location[2] - width / 2);
            light.height = height;
            light.width = width;
            light.lookAt(new THREE.Vector3(geometry.direction[0], geometry.direction[1], geometry.direction[2]));
            break;

          case 'LightStyle_WorldDirectional':
            light = new THREE.DirectionalLight();
            light.castShadow = attributes.castsShadows;
            light.position.set(geometry.location[0], geometry.location[1], geometry.location[2]);
            light.target.position.set(geometry.direction[0], geometry.direction[1], geometry.direction[2]);
            light.shadow.normalBias = 0.1;
            break;

          case 'LightStyle_WorldLinear':
            // not conversion exists, warning has already been printed to the console
            break;

          default:
            break;
        }

        if (light) {
          light.intensity = geometry.intensity;
          _color = geometry.diffuse;
          color = new THREE.Color(_color.r / 255.0, _color.g / 255.0, _color.b / 255.0);
          light.color = color;
          light.userData['attributes'] = attributes;
          light.userData['objectType'] = obj.objectType;
        }

        return light;
    }
  }

  _initLibrary() {
    if (!this.libraryPending) {
      // Load rhino3dm wrapper.
      const jsLoader = new THREE.FileLoader(this.manager);
      jsLoader.setPath(this.libraryPath);
      const jsContent = new Promise((resolve, reject) => {
        jsLoader.load('rhino3dm.js', resolve, undefined, reject);
      }); // Load rhino3dm WASM binary.

      const binaryLoader = new THREE.FileLoader(this.manager);
      binaryLoader.setPath(this.libraryPath);
      binaryLoader.setResponseType('arraybuffer');
      const binaryContent = new Promise((resolve, reject) => {
        binaryLoader.load('rhino3dm.wasm', resolve, undefined, reject);
      });
      this.libraryPending = Promise.all([jsContent, binaryContent]).then(([jsContent, binaryContent]) => {
        //this.libraryBinary = binaryContent;
        this.libraryConfig.wasmBinary = binaryContent;
        const fn = Rhino3dmWorker.toString();
        const body = ['/* rhino3dm.js */', jsContent, '/* worker */', fn.substring(fn.indexOf('{') + 1, fn.lastIndexOf('}'))].join('\n');
        this.workerSourceURL = URL.createObjectURL(new Blob([body]));
      });
    }

    return this.libraryPending;
  }

  _getWorker(taskCost) {
    return this._initLibrary().then(() => {
      if (this.workerPool.length < this.workerLimit) {
        const worker = new Worker(this.workerSourceURL);
        worker._callbacks = {};
        worker._taskCosts = {};
        worker._taskLoad = 0;
        worker.postMessage({
          type: 'init',
          libraryConfig: this.libraryConfig
        });

        worker.onmessage = e => {
          const message = e.data;

          switch (message.type) {
            case 'warning':
              this.warnings.push(message.data);
              console.warn(message.data);
              break;

            case 'decode':
              worker._callbacks[message.id].resolve(message);

              break;

            case 'error':
              worker._callbacks[message.id].reject(message);

              break;

            default:
              console.error('THREE.Rhino3dmLoader: Unexpected message, "' + message.type + '"');
          }
        };

        this.workerPool.push(worker);
      } else {
        this.workerPool.sort(function (a, b) {
          return a._taskLoad > b._taskLoad ? -1 : 1;
        });
      }

      const worker = this.workerPool[this.workerPool.length - 1];
      worker._taskLoad += taskCost;
      return worker;
    });
  }

  _releaseTask(worker, taskID) {
    worker._taskLoad -= worker._taskCosts[taskID];
    delete worker._callbacks[taskID];
    delete worker._taskCosts[taskID];
  }

  dispose() {
    for (let i = 0; i < this.workerPool.length; ++i) {
      this.workerPool[i].terminate();
    }

    this.workerPool.length = 0;
    return this;
  }

}
/* WEB WORKER */


function Rhino3dmWorker() {
  let libraryPending;
  let libraryConfig;
  let rhino;
  let taskID;

  onmessage = function (e) {
    const message = e.data;

    switch (message.type) {
      case 'init':
        // console.log(message)
        libraryConfig = message.libraryConfig;
        const wasmBinary = libraryConfig.wasmBinary;
        let RhinoModule;
        libraryPending = new Promise(function (resolve) {
          /* Like Basis THREE.Loader */
          RhinoModule = {
            wasmBinary,
            onRuntimeInitialized: resolve
          };
          rhino3dm(RhinoModule); // eslint-disable-line no-undef
        }).then(() => {
          rhino = RhinoModule;
        });
        break;

      case 'decode':
        taskID = message.id;
        const buffer = message.buffer;
        libraryPending.then(() => {
          try {
            const data = decodeObjects(rhino, buffer);
            self.postMessage({
              type: 'decode',
              id: message.id,
              data
            });
          } catch (error) {
            self.postMessage({
              type: 'error',
              id: message.id,
              error
            });
          }
        });
        break;
    }
  };

  function decodeObjects(rhino, buffer) {
    const arr = new Uint8Array(buffer);
    const doc = rhino.File3dm.fromByteArray(arr);
    const objects = [];
    const materials = [];
    const layers = [];
    const views = [];
    const namedViews = [];
    const groups = []; //Handle objects

    const objs = doc.objects();
    const cnt = objs.count;

    for (let i = 0; i < cnt; i++) {
      const _object = objs.get(i);

      const object = extractObjectData(_object, doc);

      _object.delete();

      if (object) {
        objects.push(object);
      }
    } // Handle instance definitions
    // console.log( `Instance Definitions Count: ${doc.instanceDefinitions().count()}` );


    for (let i = 0; i < doc.instanceDefinitions().count(); i++) {
      const idef = doc.instanceDefinitions().get(i);
      const idefAttributes = extractProperties(idef);
      idefAttributes.objectIds = idef.getObjectIds();
      objects.push({
        geometry: null,
        attributes: idefAttributes,
        objectType: 'InstanceDefinition'
      });
    } // Handle materials


    const textureTypes = [// rhino.TextureType.Bitmap,
    rhino.TextureType.Diffuse, rhino.TextureType.Bump, rhino.TextureType.Transparency, rhino.TextureType.Opacity, rhino.TextureType.Emap];
    const pbrTextureTypes = [rhino.TextureType.PBR_BaseColor, rhino.TextureType.PBR_Subsurface, rhino.TextureType.PBR_SubsurfaceScattering, rhino.TextureType.PBR_SubsurfaceScatteringRadius, rhino.TextureType.PBR_Metallic, rhino.TextureType.PBR_Specular, rhino.TextureType.PBR_SpecularTint, rhino.TextureType.PBR_Roughness, rhino.TextureType.PBR_Anisotropic, rhino.TextureType.PBR_Anisotropic_Rotation, rhino.TextureType.PBR_Sheen, rhino.TextureType.PBR_SheenTint, rhino.TextureType.PBR_Clearcoat, rhino.TextureType.PBR_ClearcoatBump, rhino.TextureType.PBR_ClearcoatRoughness, rhino.TextureType.PBR_OpacityIor, rhino.TextureType.PBR_OpacityRoughness, rhino.TextureType.PBR_Emission, rhino.TextureType.PBR_AmbientOcclusion, rhino.TextureType.PBR_Displacement];

    for (let i = 0; i < doc.materials().count(); i++) {
      const _material = doc.materials().get(i);

      const _pbrMaterial = _material.physicallyBased();

      let material = extractProperties(_material);
      const textures = [];

      for (let j = 0; j < textureTypes.length; j++) {
        const _texture = _material.getTexture(textureTypes[j]);

        if (_texture) {
          let textureType = textureTypes[j].constructor.name;
          textureType = textureType.substring(12, textureType.length);
          const texture = {
            type: textureType
          };
          const image = doc.getEmbeddedFileAsBase64(_texture.fileName);

          if (image) {
            texture.image = 'data:image/png;base64,' + image;
          } else {
            self.postMessage({
              type: 'warning',
              id: taskID,
              data: {
                message: `THREE.3DMLoader: Image for ${textureType} texture not embedded in file.`,
                type: 'missing resource'
              }
            });
            texture.image = null;
          }

          textures.push(texture);

          _texture.delete();
        }
      }

      material.textures = textures;

      if (_pbrMaterial.supported) {
        for (let j = 0; j < pbrTextureTypes.length; j++) {
          const _texture = _material.getTexture(pbrTextureTypes[j]);

          if (_texture) {
            const image = doc.getEmbeddedFileAsBase64(_texture.fileName);
            let textureType = pbrTextureTypes[j].constructor.name;
            textureType = textureType.substring(12, textureType.length);
            const texture = {
              type: textureType,
              image: 'data:image/png;base64,' + image
            };
            textures.push(texture);

            _texture.delete();
          }
        }

        const pbMaterialProperties = extractProperties(_material.physicallyBased());
        material = Object.assign(pbMaterialProperties, material);
      }

      materials.push(material);

      _material.delete();

      _pbrMaterial.delete();
    } // Handle layers


    for (let i = 0; i < doc.layers().count(); i++) {
      const _layer = doc.layers().get(i);

      const layer = extractProperties(_layer);
      layers.push(layer);

      _layer.delete();
    } // Handle views


    for (let i = 0; i < doc.views().count(); i++) {
      const _view = doc.views().get(i);

      const view = extractProperties(_view);
      views.push(view);

      _view.delete();
    } // Handle named views


    for (let i = 0; i < doc.namedViews().count(); i++) {
      const _namedView = doc.namedViews().get(i);

      const namedView = extractProperties(_namedView);
      namedViews.push(namedView);

      _namedView.delete();
    } // Handle groups


    for (let i = 0; i < doc.groups().count(); i++) {
      const _group = doc.groups().get(i);

      const group = extractProperties(_group);
      groups.push(group);

      _group.delete();
    } // Handle settings


    const settings = extractProperties(doc.settings()); //TODO: Handle other document stuff like dimstyles, instance definitions, bitmaps etc.
    // Handle dimstyles
    // console.log( `Dimstyle Count: ${doc.dimstyles().count()}` );
    // Handle bitmaps
    // console.log( `Bitmap Count: ${doc.bitmaps().count()}` );
    // Handle strings -- this seems to be broken at the moment in rhino3dm
    // console.log( `Document Strings Count: ${doc.strings().count()}` );

    /*
    for( var i = 0; i < doc.strings().count(); i++ ){
    		var _string= doc.strings().get( i );
    		console.log(_string);
    	var string = extractProperties( _group );
    		strings.push( string );
    		_string.delete();
    	}
    */

    doc.delete();
    return {
      objects,
      materials,
      layers,
      views,
      namedViews,
      groups,
      settings
    };
  }

  function extractObjectData(object, doc) {
    const _geometry = object.geometry();

    const _attributes = object.attributes();

    let objectType = _geometry.objectType;
    let geometry, attributes, position, data, mesh; // skip instance definition objects
    //if( _attributes.isInstanceDefinitionObject ) { continue; }
    // TODO: handle other geometry types

    switch (objectType) {
      case rhino.ObjectType.Curve:
        const pts = curveToPoints(_geometry, 100);
        position = {};
        attributes = {};
        data = {};
        position.itemSize = 3;
        position.type = 'Float32Array';
        position.array = [];

        for (let j = 0; j < pts.length; j++) {
          position.array.push(pts[j][0]);
          position.array.push(pts[j][1]);
          position.array.push(pts[j][2]);
        }

        attributes.position = position;
        data.attributes = attributes;
        geometry = {
          data
        };
        break;

      case rhino.ObjectType.Point:
        const pt = _geometry.location;
        position = {};
        const color = {};
        attributes = {};
        data = {};
        position.itemSize = 3;
        position.type = 'Float32Array';
        position.array = [pt[0], pt[1], pt[2]];

        const _color = _attributes.drawColor(doc);

        color.itemSize = 3;
        color.type = 'Float32Array';
        color.array = [_color.r / 255.0, _color.g / 255.0, _color.b / 255.0];
        attributes.position = position;
        attributes.color = color;
        data.attributes = attributes;
        geometry = {
          data
        };
        break;

      case rhino.ObjectType.PointSet:
      case rhino.ObjectType.Mesh:
        geometry = _geometry.toThreejsJSON();
        break;

      case rhino.ObjectType.Brep:
        const faces = _geometry.faces();

        mesh = new rhino.Mesh();

        for (let faceIndex = 0; faceIndex < faces.count; faceIndex++) {
          const face = faces.get(faceIndex);

          const _mesh = face.getMesh(rhino.MeshType.Any);

          if (_mesh) {
            mesh.append(_mesh);

            _mesh.delete();
          }

          face.delete();
        }

        if (mesh.faces().count > 0) {
          mesh.compact();
          geometry = mesh.toThreejsJSON();
          faces.delete();
        }

        mesh.delete();
        break;

      case rhino.ObjectType.Extrusion:
        mesh = _geometry.getMesh(rhino.MeshType.Any);

        if (mesh) {
          geometry = mesh.toThreejsJSON();
          mesh.delete();
        }

        break;

      case rhino.ObjectType.TextDot:
        geometry = extractProperties(_geometry);
        break;

      case rhino.ObjectType.Light:
        geometry = extractProperties(_geometry);

        if (geometry.lightStyle.name === 'LightStyle_WorldLinear') {
          self.postMessage({
            type: 'warning',
            id: taskID,
            data: {
              message: `THREE.3DMLoader: No conversion exists for ${objectType.constructor.name} ${geometry.lightStyle.name}`,
              type: 'no conversion',
              guid: _attributes.id
            }
          });
        }

        break;

      case rhino.ObjectType.InstanceReference:
        geometry = extractProperties(_geometry);
        geometry.xform = extractProperties(_geometry.xform);
        geometry.xform.array = _geometry.xform.toFloatArray(true);
        break;

      case rhino.ObjectType.SubD:
        // TODO: precalculate resulting vertices and faces and warn on excessive results
        _geometry.subdivide(3);

        mesh = rhino.Mesh.createFromSubDControlNet(_geometry);

        if (mesh) {
          geometry = mesh.toThreejsJSON();
          mesh.delete();
        }

        break;

      /*
      case rhino.ObjectType.Annotation:
      case rhino.ObjectType.Hatch:
      case rhino.ObjectType.ClipPlane:
      */

      default:
        self.postMessage({
          type: 'warning',
          id: taskID,
          data: {
            message: `THREE.3DMLoader: Conversion not implemented for ${objectType.constructor.name}`,
            type: 'not implemented',
            guid: _attributes.id
          }
        });
        break;
    }

    if (geometry) {
      attributes = extractProperties(_attributes);
      attributes.geometry = extractProperties(_geometry);

      if (_attributes.groupCount > 0) {
        attributes.groupIds = _attributes.getGroupList();
      }

      if (_attributes.userStringCount > 0) {
        attributes.userStrings = _attributes.getUserStrings();
      }

      if (_geometry.userStringCount > 0) {
        attributes.geometry.userStrings = _geometry.getUserStrings();
      }

      attributes.drawColor = _attributes.drawColor(doc);
      objectType = objectType.constructor.name;
      objectType = objectType.substring(11, objectType.length);
      return {
        geometry,
        attributes,
        objectType
      };
    } else {
      self.postMessage({
        type: 'warning',
        id: taskID,
        data: {
          message: `THREE.3DMLoader: ${objectType.constructor.name} has no associated mesh geometry.`,
          type: 'missing mesh',
          guid: _attributes.id
        }
      });
    }
  }

  function extractProperties(object) {
    const result = {};

    for (const property in object) {
      const value = object[property];

      if (typeof value !== 'function') {
        if (typeof value === 'object' && value !== null && value.hasOwnProperty('constructor')) {
          result[property] = {
            name: value.constructor.name,
            value: value.value
          };
        } else {
          result[property] = value;
        }
      } else {// these are functions that could be called to extract more data.
        //console.log( `${property}: ${object[ property ].constructor.name}` );
      }
    }

    return result;
  }

  function curveToPoints(curve, pointLimit) {
    let pointCount = pointLimit;
    let rc = [];
    const ts = [];

    if (curve instanceof rhino.LineCurve) {
      return [curve.pointAtStart, curve.pointAtEnd];
    }

    if (curve instanceof rhino.PolylineCurve) {
      pointCount = curve.pointCount;

      for (let i = 0; i < pointCount; i++) {
        rc.push(curve.point(i));
      }

      return rc;
    }

    if (curve instanceof rhino.PolyCurve) {
      const segmentCount = curve.segmentCount;

      for (let i = 0; i < segmentCount; i++) {
        const segment = curve.segmentCurve(i);
        const segmentArray = curveToPoints(segment, pointCount);
        rc = rc.concat(segmentArray);
        segment.delete();
      }

      return rc;
    }

    if (curve instanceof rhino.ArcCurve) {
      pointCount = Math.floor(curve.angleDegrees / 5);
      pointCount = pointCount < 2 ? 2 : pointCount; // alternative to this hardcoded version: https://stackoverflow.com/a/18499923/2179399
    }

    if (curve instanceof rhino.NurbsCurve && curve.degree === 1) {
      const pLine = curve.tryGetPolyline();

      for (let i = 0; i < pLine.count; i++) {
        rc.push(pLine.get(i));
      }

      pLine.delete();
      return rc;
    }

    const domain = curve.domain;
    const divisions = pointCount - 1.0;

    for (let j = 0; j < pointCount; j++) {
      const t = domain[0] + j / divisions * (domain[1] - domain[0]);

      if (t === domain[0] || t === domain[1]) {
        ts.push(t);
        continue;
      }

      const tan = curve.tangentAt(t);
      const prevTan = curve.tangentAt(ts.slice(-1)[0]); // Duplicated from THREE.Vector3
      // How to pass imports to worker?

      const tS = tan[0] * tan[0] + tan[1] * tan[1] + tan[2] * tan[2];
      const ptS = prevTan[0] * prevTan[0] + prevTan[1] * prevTan[1] + prevTan[2] * prevTan[2];
      const denominator = Math.sqrt(tS * ptS);
      let angle;

      if (denominator === 0) {
        angle = Math.PI / 2;
      } else {
        const theta = (tan.x * prevTan.x + tan.y * prevTan.y + tan.z * prevTan.z) / denominator;
        angle = Math.acos(Math.max(-1, Math.min(1, theta)));
      }

      if (angle < 0.1) continue;
      ts.push(t);
    }

    rc = ts.map(t => curve.pointAt(t));
    return rc;
  }
}

THREE.Rhino3dmLoader = Rhino3dmLoader;
} )();
